  // CharView.cpp : implementation file
//

#include "stdafx.h"
#include "ansieditor.h"
#include "CharView.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

/////////////////////////////////////////////////////////////////////////////
// CCharView

CCharView::CCharView() :
	m_ptHover(-1, -1),
	m_ptLastClicked(-1, -1)//,
//	m_ptHighLight(-1, -1)
{
	m_nRowCount = 0;
	m_bDowned = FALSE;

	int nGridSize = 6;
	nGridSize = gpApp->GetProfileInt("Dialog", "CharBox.GridSize", nGridSize);
	SetGridSize(nGridSize);
}

CCharView::~CCharView()
{
	ClearCharsArray();
	gpApp->WriteProfileInt("Dialog", "CharBox.GridSize", m_nGridSize);
}


BEGIN_MESSAGE_MAP(CCharView, CWnd)
	//{{AFX_MSG_MAP(CCharView)
	ON_WM_SIZE()
	ON_WM_PAINT()
	ON_WM_VSCROLL()
	ON_WM_HSCROLL()
	ON_WM_MOUSEMOVE()
	ON_WM_SYSKEYDOWN()
	ON_WM_MOUSEWHEEL()
	ON_WM_LBUTTONDOWN()
	ON_WM_LBUTTONUP()
	ON_WM_KEYDOWN()
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()


/////////////////////////////////////////////////////////////////////////////
// CCharView message handlers

void CCharView::SetChars(LPCTSTR lpszChars)
{
	m_sChars = lpszChars;
	if(!m_sChars.IsEmpty() && m_sChars[m_sChars.GetLength() - 1] != '\n')
	{
		m_sChars += '\n';
	}

	UpdateCharsArray();
	UpdateScrollSize();
	Invalidate();
	UpdateWindow();
}

void CCharView::UpdateCharsArray()
{
	BSTR bsChars = m_sChars.AllocSysString();
	BSTR pwc = bsChars;

	ClearCharsArray();
	m_nRowCount = 0;
	m_nColCount = 0;

	for(BSTR prevpwc = pwc; *pwc; ++pwc)
	{
		if(*pwc == 27 || *pwc == L'\n')
		{
			*pwc = L'\0';
			int nLen = pwc - prevpwc;
			if(nLen > m_nColCount)
			{
				m_nColCount = nLen;
			}
			BSTR bs = SysAllocStringLen(prevpwc, nLen);
			m_absChars.SetAtGrow(m_nRowCount++, bs);
			prevpwc = pwc + 1;
		}
	}

	SysFreeString(bsChars);
}

void CCharView::ClearCharsArray()
{
	for(int r = 0; r <= m_absChars.GetUpperBound(); ++r)
	{
		SysFreeString(m_absChars[r]);
	}
	m_absChars.RemoveAll();
}

void CCharView::OnSysKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) 
{
	if(GetCapture() == this)
	{
		ReleaseCapture();
		m_ptHover = CPoint(-1, -1);
		Invalidate();
		UpdateWindow();	
	}
}

void CCharView::OnSize(UINT nType, int cx, int cy) 
{
	CWnd::OnSize(nType, cx, cy);

	UpdateScrollSize();
}

void CCharView::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar) 
{
	DoScroll(SB_VERT, nSBCode, nPos);
}

void CCharView::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar) 
{
	DoScroll(SB_HORZ, nSBCode, nPos);
}

void CCharView::DoScroll(int fnBar, UINT nSBCode, UINT nPos)
{
	SCROLLINFO si;
	si.cbSize = sizeof(SCROLLINFO);
	GetScrollInfo(fnBar, &si, SIF_PAGE|SIF_POS);
	int nOrig = si.nPos;
	int nn = nOrig;

	switch (nSBCode)
	{
	case SB_TOP:
		nn = 0;
		break;
	case SB_BOTTOM:
		nn = INT_MAX;
		break;
	case SB_LINEUP:
		--nn;
		break;
	case SB_LINEDOWN:
		++nn;
		break;
	case SB_PAGEUP:
		nn -= si.nPage;
		break;
	case SB_PAGEDOWN:
		nn += si.nPage;
		break;
	case SB_THUMBTRACK:
		nn = nPos;
		break;
	}

	int nMax = GetScrollLimit(fnBar);
	if(nn < 0)
	{
		nn = 0;
	}
	if(nn > nMax)
	{
		nn = nMax;
	}
	
	int cn = nn - nOrig;
	if(cn)
	{
		if(fnBar == SB_VERT)
		{
			ScrollWindow(0, - cn * m_nCellHeight);
		}
		else
		{
			ScrollWindow(- cn * m_nCellWidth, 0);
		}
		SetScrollPos(fnBar, nn);
		Invalidate();
		UpdateWindow();
	}
}

void CCharView::UpdateScrollSize()
{
	CRect rect;
	GetClientRect(rect);
	SCROLLINFO si;
	si.cbSize = sizeof(SCROLLINFO);
	si.fMask = SIF_RANGE|SIF_PAGE;

	si.nMin = 0;
	si.nMax = m_nColCount - 1;
	si.nPage = rect.Width() / m_nCellWidth;
	SetScrollInfo(SB_HORZ, &si, FALSE);

	si.nMin = 0;
	si.nMax = m_nRowCount - 1;
	si.nPage = rect.Height() / m_nCellHeight;

	SetScrollInfo(SB_VERT, &si, TRUE);
}

void CCharView::RedrawGrid(int nRow, int nCol)
{
	if(nRow == -1)
	{
		return;
	}
	CClientDC dc(this);
	OnPrepareDC(&dc);
	CRect rect(nCol * m_nCellWidth, nRow * m_nCellHeight, nCol * m_nCellWidth + m_nCellWidth, nRow * m_nCellHeight + m_nCellHeight);
	dc.LPtoDP(rect);
	InvalidateRect(rect);
	UpdateWindow();
}

void CCharView::OnPrepareDC(CDC *pDC, CPrintInfo *pInfo)
{
	pDC->SetMapMode(MM_TEXT);
	
	CPoint ptOrg(-GetScrollPos(SB_HORZ) * m_nCellWidth - 1, -GetScrollPos(SB_VERT) * m_nCellHeight - 1);
	pDC->SetViewportOrg(ptOrg);
}

void CCharView::OnPaint() 
{
	CPaintDC dcPaint(this);
	CRect rcInvalid;
	dcPaint.GetClipBox(rcInvalid);
	OnPrepareDC(&dcPaint);
	dcPaint.DPtoLP(rcInvalid);

	CDC dc;
	dc.CreateCompatibleDC(&dcPaint);
	CBitmap bmp;
	bmp.CreateCompatibleBitmap(&dcPaint, rcInvalid.Width(), rcInvalid.Height());
	CBitmap *pOldBitmap = dc.SelectObject(&bmp);
	CFont font;
	font.CreatePointFont(m_nFontPoint, gpApp->m_options.sDefaultFont, &dc);
	CFont *pOldFont = (CFont*)dc.SelectObject(&font);
	HDC hdc = dc.GetSafeHdc();

	dc.SetViewportOrg(-rcInvalid.left, -rcInvalid.top);
	dc.FillSolidRect(rcInvalid, GetSysColor(COLOR_WINDOW));

	CRect rcGrid;
	rcGrid.top = rcInvalid.top / m_nCellHeight;
	rcGrid.bottom = rcInvalid.bottom / m_nCellHeight + ((rcInvalid.bottom % m_nCellHeight)? 1: 0);
	rcGrid.left = rcInvalid.left / m_nCellWidth;
	rcGrid.right = rcInvalid.right / m_nCellWidth + ((rcInvalid.right % m_nCellWidth)? 1: 0);

	if(rcGrid.left < 0)
	{
		rcGrid.left = 0;
	}
	if(rcGrid.top < 0)
	{
		rcGrid.top = 0;
	}

	int c, r, tx, ty, tLen;
	for(r = rcGrid.top; r <= rcGrid.bottom ; ++r)
	{
		ty = r * m_nCellHeight;
		dc.MoveTo(rcInvalid.left, ty);
		dc.LineTo(rcInvalid.right, ty);
	}
	for(c = rcGrid.left; c <= rcGrid.right ; ++c)
	{
		tx = c * m_nCellWidth;
		dc.MoveTo(tx, rcInvalid.top);
		dc.LineTo(tx, rcInvalid.bottom);
	}

	dc.SetBkMode(TRANSPARENT);
	int nRealRowEnd = min(rcGrid.bottom, m_nRowCount);
	for(r = rcGrid.top; r < nRealRowEnd; ++r)
	{
		BSTR bs = m_absChars[r];
		tLen = SysStringLen(bs);
		int nRealColEnd = min(rcGrid.right, tLen);
		for(c = rcGrid.left; c < nRealColEnd; ++c)
		{
			TextOutW(hdc, c * m_nCellWidth + 4, r * m_nCellHeight + 4, bs + c, 1);
		}
	}

	if(!IsGridDummy(m_ptLastClicked))
	{
		dc.SetTextColor(GetSysColor(COLOR_HIGHLIGHTTEXT));
		CRect rc(m_ptLastClicked.x * m_nCellWidth + 2, m_ptLastClicked.y * m_nCellHeight + 2, m_ptLastClicked.x * m_nCellWidth + m_nCellWidth - 1, m_ptLastClicked.y * m_nCellHeight + m_nCellHeight - 1);
		dc.FillSolidRect(rc, RGB(0, 95, 215));
		TextOutW(hdc, m_ptLastClicked.x * m_nCellWidth + 4, m_ptLastClicked.y * m_nCellWidth + 4, m_absChars[m_ptLastClicked.y] + m_ptLastClicked.x, 1);
	}
	if(!IsGridDummy(m_ptHover))
	{
		//CRect rc(m_ptHover.x * m_nCellWidth + 2, m_ptHover.y * m_nCellHeight + 2, m_ptHover.x * m_nCellWidth + 17, m_ptHover.y * m_nCellHeight + 17);
		CRect rc(m_ptHover.x * m_nCellWidth + 2, m_ptHover.y * m_nCellHeight + 2, m_ptHover.x * m_nCellWidth + m_nCellWidth - 1, m_ptHover.y * m_nCellHeight + m_nCellHeight - 1);
		if(m_bDowned)
		{
			dc.SetTextColor(GetSysColor(COLOR_HIGHLIGHTTEXT));
			dc.FillSolidRect(rc, RGB(255, 0, 255));
		}
		else
		{
			dc.SetTextColor(GetSysColor(COLOR_WINDOWTEXT));
			dc.FillSolidRect(rc, RGB(255, 255, 0));
		}
		TextOutW(hdc, m_ptHover.x * m_nCellWidth + 4, m_ptHover.y * m_nCellHeight + 4, m_absChars[m_ptHover.y] + m_ptHover.x, 1);
	}

	dcPaint.BitBlt(rcInvalid.left, rcInvalid.top, rcInvalid.Width(), rcInvalid.Height(), &dc, rcInvalid.left, rcInvalid.top, SRCCOPY);

	dc.SelectObject(pOldFont);
	dc.SelectObject(pOldBitmap);


}

void CCharView::HighLightGrid(int nRow, int nCol)
{
	CPoint ptNew(nCol, nRow);
	if(m_ptHover!= ptNew)
	{
		CPoint ptOld = m_ptHover;
		m_ptHover = CPoint(-1, -1);
		RedrawGrid(ptOld);
		m_ptHover = ptOld;

		if(ptNew.x >=0  && ptNew.y >=0 && ptNew.y < m_nRowCount)
		{
			m_ptHover.y = ptNew.y;
			if(ptNew.x < (int)SysStringLen(m_absChars[ptNew.y]) && m_absChars[ptNew.y][ptNew.x] != 32)
			{
				m_ptHover = ptNew;
				RedrawGrid(ptNew);
				return;
			}
		}
	}
}

void CCharView::OnMouseMove(UINT nFlags, CPoint point) 
{
	static CPoint pointOld;
	CPoint ptScreen(point);
	ClientToScreen(&ptScreen);

	if(pointOld == point)
	{
		return;
	}
	pointOld = point;
	
	CRect rect;
	GetClientRect(rect);
	if(rect.PtInRect(point) && WindowFromPoint(ptScreen) == this)
	{
		if(GetCapture() != this)
		{
			SetCapture();
		}
		CClientDC dc(this);
		OnPrepareDC(&dc);
		dc.DPtoLP(&point);
		HighLightGrid((point.y - 1) / m_nCellWidth, (point.x - 1) / m_nCellHeight);
	}
	else
	{
		HighLightGrid(-1, -1);
		ReleaseCapture();
	}
}

BOOL CCharView::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt) 
{
	BOOL bControl = (nFlags & MK_CONTROL);
	UINT message = bControl? WM_HSCROLL: WM_VSCROLL;
	UINT nSBCode;

	SCROLLINFO si;
	si.cbSize = sizeof(SCROLLINFO);
	si.fMask = SIF_PAGE;
	GetScrollInfo(bControl? SB_HORZ: SB_VERT, &si, SIF_PAGE);

	UINT nStep = gpApp->m_options.nScrollStep;
	if(nStep == 0 || nStep > si.nPage)
	{
		nSBCode = (zDelta > 0)? SB_PAGEUP: SB_PAGEDOWN;
		SendMessage(message, nSBCode);
	}
	else
	{
		nSBCode = (zDelta > 0)? SB_LINEUP: SB_LINEDOWN;
		for(UINT n = 0; n < nStep; n++)
		{
			SendMessage(message, nSBCode);
		}
	}
	
	ScreenToClient(&pt);
	OnMouseMove(nFlags, pt);
	return TRUE;
}

void CCharView::OnLButtonDown(UINT nFlags, CPoint point) 
{
	if(m_ptHover.x != -1)
	{
		m_bDowned = TRUE;
		RedrawGrid(m_ptHover);
	}
}

void CCharView::OnLButtonUp(UINT nFlags, CPoint point) 
{
	if(m_bDowned)
	{
		m_bDowned = FALSE;
		ClickGrid(m_ptHover);
	}
}


//si.nPage = rect.Width() / m_nCellWidth;
void CCharView::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) 
{
	if(nChar == VK_LWIN)
	{
		return;
	}
	CClientDC dc(this);
	OnPrepareDC(&dc);
	CRect rect;
	GetClientRect(rect);
	dc.DPtoLP(rect);

	CPoint ptHover(m_ptHover);
	switch(nChar)
	{
	case VK_LEFT:
		--ptHover.x;
		break;
	case VK_RIGHT:
		++ptHover.x;
		break;
	case VK_UP:
		--ptHover.y;
		break;
	case VK_DOWN:
		++ptHover.y;
		break;
	case VK_PRIOR:
		ptHover.y -= rect.Height() / m_nCellWidth;
		break;
	case VK_NEXT:
		ptHover.y += rect.Height() / m_nCellWidth;
		break;
	case VK_HOME:
		//ptHover.x = 
		ptHover.y = 0;
		break;
	case VK_END:
		//ptHover.x = 0;
		ptHover.y = 65535;
		break;
	case VK_RETURN:
		// pass through
	case VK_NUMPAD0:
		//
		ClickGrid(m_ptHover);
		return;
	case 0xBB:
		// pass through
	case 0x6B: // +
		SetGridSize(m_nGridSize + 1);
		return;
	case 0xBD:
		// pass through
	case 0x6D: // -
		SetGridSize(m_nGridSize - 1);
		return;
	case 0xBF: // /
		// pass through
	case 0x6F: // /
		SetGridSize(6);
		return;
	default:
//		afxDump << nChar << "#\n";
		return;
	}
	if(ptHover.y < 0)
	{
		ptHover.y = 0;
	}
	if(ptHover.y > m_nRowCount - 1)
	{
		ptHover.y = m_nRowCount - 1;
	}
	int nLen = SysStringLen(m_absChars[ptHover.y]);
	if(nLen && ptHover.x > nLen - 1)
	{
		ptHover.x = nLen - 1;
	}
	if(ptHover.x < 0)
	{
		ptHover.x = 0;
	}
	BOOL bHorzScroll = FALSE;
	BOOL bVertScroll = FALSE;
	if(ptHover.y > (rect.bottom / m_nCellHeight - 1))
	{
		bVertScroll = TRUE;
	}
	else if(ptHover.y < (rect.top / m_nCellHeight + 1))
	{
		bVertScroll = TRUE;
	}
	if(ptHover.x > (rect.right / m_nCellWidth - 1))
	{
		bHorzScroll = TRUE;
	}
	else if(ptHover.x < (rect.left / m_nCellWidth + 1))
	{
		bHorzScroll = TRUE;
	}
	if(bHorzScroll)
	{
		SetScrollPos(SB_HORZ, ptHover.x - int(rect.Width() / m_nCellWidth / 3.0));
	}
	if(bVertScroll)
	{
		SetScrollPos(SB_VERT, ptHover.y - int(rect.Height() / m_nCellHeight / 3.0));
	}
	if(bHorzScroll || bVertScroll)
	{
		Invalidate(FALSE);
		UpdateWindow();
	}
	HighLightGrid(ptHover.y, ptHover.x);
	CWnd::OnKeyDown(nChar, nRepCnt, nFlags);
}

void CCharView::ClickGrid(CPoint pt)
{
	if(!IsGridDummy(pt))
	{
		CPoint ptLastClicked = m_ptLastClicked;
		m_ptLastClicked = CPoint(-1, -1);
		RedrawGrid(ptLastClicked);

		RedrawGrid(pt);
		m_ptLastClicked = pt;

		wchar_t wc = *(m_absChars[pt.y] + pt.x);
		CString s(&wc, 1);
		CWnd *pWnd = GetFocus();
		if(pWnd == NULL)
		{
			pWnd = GetAppContext()->m_pView;
			pWnd->SetFocus();
			//return;
		}
		pWnd->SendMessage(WM_IME_STARTCOMPOSITION);
		for(int n = 0; n < s.GetLength(); n++)
		{
			pWnd->SendMessage(WM_CHAR, s[n] & 0xFF, DO_NOT_IGNORE_CHAR_FLAG);
		}
		gpFrame->AddMruChar(wc);
	}
}

void CCharView::SetGridSize(int nGridSize)
{
	if(nGridSize < 3)
	{
		nGridSize = 3;
	}
	if(nGridSize > 30)
	{
		nGridSize = 30;
	}
	m_nFontPoint = 15 * nGridSize;	// +-15
	m_nCellWidth = (m_nFontPoint / 15) * 2 + 6;
	m_nCellHeight = m_nCellWidth;
	m_nGridSize = nGridSize;
	if(::IsWindow(GetSafeHwnd()))
	{
		//
		UpdateScrollSize();
		Invalidate();
		UpdateWindow();
	}
}



